﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;

namespace LowLevelGraphics.Filter
{
    /// <summary>
    /// Contrast types
    /// </summary>
    public enum CONTRAST_TYPE
    {
        /// <summary>
        /// I - Ib / Ib
        /// </summary>
        WEBER,

        /// <summary>
        /// Imax - Imin / (Imax + Imin)
        /// </summary>
        MICHELSON,

        /// <summary>
        /// No contrast at all
        /// </summary>
        NONE,

        /// <summary>
        /// 
        /// </summary>
        DEFAULT,

        /// <summary>
        /// Own method
        /// </summary>
        DR4G0N76,

        /// <summary>
        /// SQRT(1 / (M*N)* SUM(0, N-1) * SUM(0, M-1)(Iij-I)²)
        /// </summary>
        RMS
    }


    /// <summary>
    /// Enables to change the contrast of a bitmap
    /// </summary>
    public class Contrast : BaseImageFilter
    {
        protected sbyte m_nContrast = 10;
        protected CONTRAST_TYPE m_CONTRAST_TYPE = CONTRAST_TYPE.DEFAULT;

        /// <summary>
        /// default constructor
        /// </summary>
        public Contrast()
        {
        }

        /// <summary>
        /// specify contrast directly
        /// </summary>
        /// <param name="_nContrast"></param>
        public Contrast(sbyte _nContrast)
        {
            m_nContrast = _nContrast;
        }

        /// <summary>
        /// Used by clone to create a deep copy
        /// </summary>
        /// <param name="_contrast"></param>
        internal Contrast(Contrast _contrast)
        {
            this.m_nContrast = _contrast.m_nContrast;
            this.m_CONTRAST_TYPE = _contrast.m_CONTRAST_TYPE;
        }

        /// <summary>
        /// Gets or sets contrast intensity
        /// </summary>
        public sbyte Intensity
        {
            get { return m_nContrast; }
            set { m_nContrast = value; }
        }

        /// <summary>
        /// Choose contrast for bitmap.
        /// </summary>
        /// <param name="_bitmap"></param>
        /// <returns></returns>
        public override UnsafeBitmap Execute(UnsafeBitmap _bitmap)
        {
            UnsafeBitmap bitmap = _bitmap;
            if (m_nContrast < -100) return _bitmap;
            if (m_nContrast > 100) return _bitmap;

            double dContrast = (100.0 + m_nContrast) / 100.0;

            dContrast *= dContrast;

            int nWidth = bitmap.Width;
            int nHeight = bitmap.Height;

            // GDI+ still lies to us - the return format is BGR, NOT RGB.
            BitmapData bmData = bitmap.BitmapData; //bitmap.LockBits(new Rectangle(0, 0, nWidth, nHeight), ImageLockMode.ReadWrite, PixelFormat.Format24bppRgb);

            int stride = bmData.Stride;
            IntPtr Scan0 = bmData.Scan0;

            unsafe
            {
                int nOffset = bitmap.OffsetPointerIncrease;
                int nByteCount = bitmap.BytesPerPixelCount;

                switch (m_CONTRAST_TYPE)
                {
                    case CONTRAST_TYPE.MICHELSON:
                        MichelsonContrast(Scan0, nByteCount, nOffset, dContrast, nWidth, nHeight);
                        break;

                    case CONTRAST_TYPE.DR4G0N76:
                        dr4g0n76Contrast(Scan0, nByteCount, nOffset, dContrast, nWidth, nHeight);
                        break;

                    case CONTRAST_TYPE.RMS:
                        RmsContrast(Scan0, nByteCount, nOffset, dContrast, nWidth, nHeight);
                        break;

                    case CONTRAST_TYPE.WEBER:
                        WeberContrast(Scan0, nByteCount, nOffset, dContrast, nWidth, nHeight);
                        break;

                    case CONTRAST_TYPE.DEFAULT:
                    default:
                        DefaultContrast(Scan0, nByteCount, nOffset, dContrast, nWidth, nHeight);
                        break;
                }
            }
            return _bitmap;
        }

        /// <summary>
        /// This is also known as visibility
        /// </summary>
        private void MichelsonContrast(IntPtr Scan0, int nByteCount, int nOffset, double dContrast, int nWidth, int nHeight)
        {
            unsafe
            {
                byte* p = (byte*)(void*)Scan0;
                int nRed = 0;
                int nGreen = 0;
                int nBlue = 0;

                double dPixel = 0;
                int x = 0;
                int y = 0;

                for (y = 0; y < nHeight; ++y)
                {
                    for (x = 0; x < nWidth; ++x)
                    {
                        nBlue = p[0];
                        nGreen = p[1];
                        nRed = p[2];

                        dPixel = nRed / 255.0;
                        dPixel -= 0.5;
                        dPixel *= dContrast;
                        dPixel += 0.5;
                        dPixel *= 255;
                        if (dPixel < 0) dPixel = 0;
                        if (dPixel > 255) dPixel = 255;
                        p[2] = (byte)dPixel;

                        dPixel = nGreen / 255.0;
                        dPixel -= 0.5;
                        dPixel *= dContrast;
                        dPixel += 0.5;
                        dPixel *= 255;
                        if (dPixel < 0) dPixel = 0;
                        if (dPixel > 255) dPixel = 255;
                        p[1] = (byte)dPixel;

                        dPixel = nBlue / 255.0;
                        dPixel -= 0.5;
                        dPixel *= dContrast;
                        dPixel += 0.5;
                        dPixel *= 255;
                        if (dPixel < 0) dPixel = 0;
                        if (dPixel > 255) dPixel = 255;
                        p[0] = (byte)dPixel;

                        p += nByteCount;
                    }
                    p += nOffset;
                }
            }

        }

        /// <summary>
        /// own contrast Method
        /// </summary>
        private void dr4g0n76Contrast(IntPtr Scan0, int nByteCount, int nOffset, double dContrast, int nWidth, int nHeight)
        {
            unsafe
            {
                byte* p = (byte*)(void*)Scan0;
                int nRed = 0;
                int nGreen = 0;
                int nBlue = 0;

                double dPixel = 0;
                int x = 0;
                int y = 0;

                for (y = 0; y < nHeight; ++y)
                {
                    for (x = 0; x < nWidth; ++x)
                    {
                        nBlue = p[0];
                        nGreen = p[1];
                        nRed = p[2];

                        dPixel = nRed / 255.0;
                        dPixel -= 0.5;
                        dPixel *= dContrast;
                        dPixel += 0.5;
                        dPixel *= 255;
                        if (dPixel < 0) dPixel = 0;
                        if (dPixel > 255) dPixel = 255;
                        p[2] = (byte)dPixel;

                        dPixel = nGreen / 255.0;
                        dPixel -= 0.5;
                        dPixel *= dContrast;
                        dPixel += 0.5;
                        dPixel *= 255;
                        if (dPixel < 0) dPixel = 0;
                        if (dPixel > 255) dPixel = 255;
                        p[1] = (byte)dPixel;

                        dPixel = nBlue / 255.0;
                        dPixel -= 0.5;
                        dPixel *= dContrast;
                        dPixel += 0.5;
                        dPixel *= 255;
                        if (dPixel < 0) dPixel = 0;
                        if (dPixel > 255) dPixel = 255;
                        p[0] = (byte)dPixel;

                        p += nByteCount;
                    }
                    p += nOffset;
                }
            }
        }

        /// <summary>
        /// Rms Contrast 1/(M*N)
        /// </summary>
        private void RmsContrast(IntPtr Scan0, int nByteCount, int nOffset, double dContrast, int nWidth, int nHeight)
        {
            unsafe
            {
                byte* p = (byte*)(void*)Scan0;
                int nRed = 0;
                int nGreen = 0;
                int nBlue = 0;

                double dPixel = 0;
                int x = 0;
                int y = 0;

                for (y = 0; y < nHeight; ++y)
                {
                    for (x = 0; x < nWidth; ++x)
                    {
                        nBlue = p[0];
                        nGreen = p[1];
                        nRed = p[2];

                        dPixel = nRed / 255.0;
                        dPixel -= 0.5;
                        dPixel *= dContrast;
                        dPixel += 0.5;
                        dPixel *= 255;
                        if (dPixel < 0) dPixel = 0;
                        if (dPixel > 255) dPixel = 255;
                        p[2] = (byte)dPixel;

                        dPixel = nGreen / 255.0;
                        dPixel -= 0.5;
                        dPixel *= dContrast;
                        dPixel += 0.5;
                        dPixel *= 255;
                        if (dPixel < 0) dPixel = 0;
                        if (dPixel > 255) dPixel = 255;
                        p[1] = (byte)dPixel;

                        dPixel = nBlue / 255.0;
                        dPixel -= 0.5;
                        dPixel *= dContrast;
                        dPixel += 0.5;
                        dPixel *= 255;
                        if (dPixel < 0) dPixel = 0;
                        if (dPixel > 255) dPixel = 255;
                        p[0] = (byte)dPixel;

                        p += nByteCount;
                    }
                    p += nOffset;
                }
            }
        }

        /// <summary>
        /// Weber contrast
        /// </summary>
        private void WeberContrast(IntPtr Scan0, int nByteCount, int nOffset, double dContrast, int nWidth, int nHeight)
        {
            unsafe
            {
                byte* p = (byte*)(void*)Scan0;
                int nRed = 0;
                int nGreen = 0;
                int nBlue = 0;

                double dPixel = 0;
                int x = 0;
                int y = 0;

                for (y = 0; y < nHeight; ++y)
                {
                    for (x = 0; x < nWidth; ++x)
                    {
                        nBlue = p[0];
                        nGreen = p[1];
                        nRed = p[2];

                        dPixel = nRed / 255.0;
                        dPixel -= 0.5;
                        dPixel *= dContrast;
                        dPixel += 0.5;
                        dPixel *= 255;
                        if (dPixel < 0) dPixel = 0;
                        if (dPixel > 255) dPixel = 255;
                        p[2] = (byte)dPixel;

                        dPixel = nGreen / 255.0;
                        dPixel -= 0.5;
                        dPixel *= dContrast;
                        dPixel += 0.5;
                        dPixel *= 255;
                        if (dPixel < 0) dPixel = 0;
                        if (dPixel > 255) dPixel = 255;
                        p[1] = (byte)dPixel;

                        dPixel = nBlue / 255.0;
                        dPixel -= 0.5;
                        dPixel *= dContrast;
                        dPixel += 0.5;
                        dPixel *= 255;
                        if (dPixel < 0) dPixel = 0;
                        if (dPixel > 255) dPixel = 255;
                        p[0] = (byte)dPixel;

                        p += nByteCount;
                    }
                    p += nOffset;
                }
            }
        }

        /// <summary>
        /// Default contrast
        /// </summary>
        /// <param name="p"></param>
        /// <param name="dContrast"></param>
        /// <param name="nWidht"></param>
        /// <param name="nHeight"></param>
        private unsafe void DefaultContrast(IntPtr Scan0, int nByteCount, int nOffset, double dContrast, int nWidth, int nHeight)
        {
            byte* p = (byte*)(void*)Scan0;
            int nRed = 0;
            int nGreen = 0;
            int nBlue = 0;

            double dPixel = 0;
            int x = 0;
            int y = 0;

            for (y = 0; y < nHeight; ++y)
            {
                for (x = 0; x < nWidth; ++x)
                {
                    nBlue = p[0];
                    nGreen = p[1];
                    nRed = p[2];

                    dPixel = nRed / 255.0;
                    dPixel -= 0.5;
                    dPixel *= dContrast;
                    dPixel += 0.5;
                    dPixel *= 255;
                    if (dPixel < 0) dPixel = 0;
                    if (dPixel > 255) dPixel = 255;
                    p[2] = (byte)dPixel;

                    dPixel = nGreen / 255.0;
                    dPixel -= 0.5;
                    dPixel *= dContrast;
                    dPixel += 0.5;
                    dPixel *= 255;
                    if (dPixel < 0) dPixel = 0;
                    if (dPixel > 255) dPixel = 255;
                    p[1] = (byte)dPixel;

                    dPixel = nBlue / 255.0;
                    dPixel -= 0.5;
                    dPixel *= dContrast;
                    dPixel += 0.5;
                    dPixel *= 255;
                    if (dPixel < 0) dPixel = 0;
                    if (dPixel > 255) dPixel = 255;
                    p[0] = (byte)dPixel;

                    p += nByteCount;
                }
                p += nOffset;
            }
        }

        /// <summary>
        /// create a deep copy
        /// </summary>
        /// <returns></returns>
        public override object Clone()
        {
            return new Contrast(this);
        }
    }
}
